#include <Http.h>
#include <string.h>
#include <WinError.h>

#include "download_server.h"


static HTTPAPI_VERSION http_api_version = HTTPAPI_VERSION_1;
static HTTP_VERSION http_version = HTTP_VERSION_1_1;

typedef struct _pending_t {
	int checksum;
	gf16_t * coefs;
} pending_t;

struct download_server_t {
	gequations_t * equs;
	HANDLE request_queue;
	WCHAR urlPrefix[256];
	
	int runable;
};

void download_server_init()
{
	HttpInitialize(http_api_version, HTTP_INITIALIZE_SERVER, NULL);
}

void download_server_finalize()
{
	HttpTerminate(HTTP_INITIALIZE_SERVER, NULL);
}

download_server_t * create_download_server(char * PORT, gequations_t * equations)
{
	download_server_t * server = (download_server_t *) malloc(sizeof(download_server_t));
	ULONG result;

	server->equs = equations;
	swprintf(server->urlPrefix, 200, L"http://+:%S/", PORT);
	server->runable = 0;
	result = HttpCreateHttpHandle(&(server->request_queue), 0);
	result = HttpAddUrl(server->request_queue, server->urlPrefix, NULL);
	
	return server;
}

void destroy_download_server(download_server_t * server)
{
	HttpRemoveUrl(server->request_queue, server->urlPrefix);

	free(server);
}

int getHttpHeaderIntValue(PHTTP_REQUEST request, const char * key)
{
	int i = request->Headers.UnknownHeaderCount;
	int value;
	HTTP_UNKNOWN_HEADER * p = request->Headers.pUnknownHeaders;
	for (;i>0;) {
		--i;
		if (!strncmp(p->pName, key, p->NameLength)) {
			if (_snscanf_s(p->pRawValue, p->RawValueLength, "%d", &value)!=1) {
				value = -2;
			}
			return value;
		}
		++p;
	}
	return -2;
}

#define NUMBER_LEN_MAX 32

static const char * const PCSTR_QUERY = "/QUERY";
static const char * const PCSTR_FETCH = "/FETCH";
static const char * const PCSTR_CHECKSUM = "CHECKSUM";
#define CHECKSUM_STR_LEN 8
static const char * const PCSTR_INDEX = "INDEX";
#define INDEX_STR_LEN 5

#define STATUS_CODE_OK 200
static const char * const PCSTR_OK = "OK";
#define OK_STR_LEN 2

#define STATUS_CODE_NOTFOUND 404
static const char * const PCSTR_NOTFOUND = "NOT FOUND";
#define NOTFOUND_STR_LEN 11

void download_server_run(download_server_t * server, int max_pending)
{

	char str_checksum[NUMBER_LEN_MAX+1];
	char str_index[NUMBER_LEN_MAX+1];

	char str_CoefsLength[NUMBER_LEN_MAX+1];
	USHORT coefsLength_len;
	char str_BlockLength[NUMBER_LEN_MAX+1];
	USHORT blockLength_len;

	int i;

//	OVERLAPPED oOverlapped;
	HTTP_REQUEST request[1000];
	ULONG byte_number;

	gf16_t * real_coefs;
	ULONG coefs_length;

	gf16block_t block;
	ULONG block_length;

	pending_t * pending_queue;
	int pending_index = 0;

	int received_check, response_check, received_index;

	BOOL notfound;
	ULONG success;

	HTTP_RESPONSE response;
	HTTP_UNKNOWN_HEADER headers[2];

	HTTP_DATA_CHUNK datachunks[1];

	gf16_t gg;

	if (server->runable) {
		return;
	}

	printf("Server Running on %S\n", server->urlPrefix);

	server->runable = 1;

	real_coefs = new_coefs(server->equs);
	coefs_length = get_coefs_length(server->equs);
	block = new_block(server->equs);
	block_length = get_block_length(server->equs);

	coefsLength_len = sprintf_s(str_CoefsLength, NUMBER_LEN_MAX, "%d", coefs_length);
	blockLength_len = sprintf_s(str_BlockLength, NUMBER_LEN_MAX, "%d", block_length);
	
	pending_queue = (pending_t *) malloc(max_pending*sizeof(pending_t));

	response.Flags = 0;
	response.Version = http_version;
	response.EntityChunkCount = 1;
	response.pEntityChunks = datachunks;
	response.Headers.TrailerCount = 0;
	response.Headers.pTrailers = NULL;
	response.Headers.pUnknownHeaders = headers;
	memset(response.Headers.KnownHeaders, 0, HttpHeaderResponseMaximum*sizeof(HTTP_KNOWN_HEADER));

	headers[0].NameLength = CHECKSUM_STR_LEN;
	headers[0].pName = PCSTR_CHECKSUM;
	headers[0].pRawValue = str_checksum;

	headers[1].NameLength = INDEX_STR_LEN;
	headers[1].pName = PCSTR_INDEX;
	headers[1].pRawValue = str_index;

	datachunks[0].DataChunkType = HttpDataChunkFromMemory;

	for (i=0; i<max_pending; ++i) {
		pending_queue[i].checksum = -1;
		pending_queue[i].coefs = new_coefs(server->equs);
	}

	for (;server->runable;) {
		//do {
		//	memset(&oOverlapped, 0, sizeof(OVERLAPPED));
		//	HttpReceiveHttpRequest(
		//		server->request_queue,
		//		0,
		//		0,
		//		&request,
		//		sizeof(HTTP_REQUEST),
		//		NULL,
		//		&oOverlapped
		//	);
		//} while (server->runable && !GetOverlappedResult(server->request_queue, &oOverlapped, &byte_number, FALSE));
		request->RequestId = HTTP_NULL_ID;
		do {
			success = HttpReceiveHttpRequest(
				server->request_queue, 
				request->RequestId, 
				0, 
				request, 
				sizeof(HTTP_REQUEST), 
				&byte_number, 
				NULL
			);
		} while (success == ERROR_MORE_DATA);
		if (!server->runable) {
			break;
		}
		if (success!=NO_ERROR) {
			continue;
		}

		notfound = TRUE;
		if (request->Verb==HttpVerbGET && request->RawUrlLength>0) {
			if (!strncmp(request->pRawUrl, PCSTR_QUERY, request->RawUrlLength)) {
				if ((received_check = getHttpHeaderIntValue(request, PCSTR_CHECKSUM))>=0) {
					notfound = FALSE;

					response_check = rand();
					produce_coefs(server->equs, pending_queue[pending_index].coefs, real_coefs);
					response.StatusCode = STATUS_CODE_OK;
					response.ReasonLength = OK_STR_LEN;
					response.pReason = PCSTR_OK;

					response.Headers.KnownHeaders[HttpHeaderContentLength].RawValueLength = coefsLength_len;
					response.Headers.KnownHeaders[HttpHeaderContentLength].pRawValue = str_CoefsLength;

					response.Headers.UnknownHeaderCount = 2;

					response.EntityChunkCount = 0;

					headers[0].RawValueLength = sprintf_s(str_checksum, NUMBER_LEN_MAX, "%d", response_check);
					headers[1].RawValueLength = sprintf_s(str_index, NUMBER_LEN_MAX, "%d", pending_index);

					//for(;;) {
					//	memset(&oOverlapped, 0, sizeof(OVERLAPPED));
					//	HttpSendHttpResponse(
					//		server->request_queue,
					//		request.RequestId,
					//		0,
					//		&response,
					//		NULL,
					//		NULL,
					//		NULL,
					//		0,
					//		&oOverlapped,
					//		NULL
					//	);
					//	success = GetOverlappedResult(server->request_queue, &oOverlapped, &byte_number, FALSE);
					//	if (!server->runable || success || GetLastError()!=ERROR_IO_INCOMPLETE) {
					//		break;
					//	}
					//}


					success = HttpSendHttpResponse(
						server->request_queue,
						request->RequestId, 
						HTTP_SEND_RESPONSE_FLAG_MORE_DATA,
						&response,
						NULL,
						&byte_number,
						NULL,
						0,
						NULL, 
						NULL
					);

					if (!server->runable) {
						break;
					}

					if (success==NO_ERROR) {
						response.EntityChunkCount = 1;

						datachunks[0].FromMemory.BufferLength = coefs_length;
						datachunks[0].FromMemory.pBuffer = real_coefs;
						//for(;;) {
						//	memset(&oOverlapped, 0, sizeof(OVERLAPPED));
						//	HttpSendResponseEntityBody(
						//		server->request_queue,
						//		request.RequestId,
						//		0,
						//		1,
						//		datachunks,
						//		NULL,
						//		NULL,
						//		0,
						//		&oOverlapped,
						//		NULL
						//	);
						//	success = GetOverlappedResult(server->request_queue, &oOverlapped, &byte_number, FALSE);
						//	if (!server->runable || success || GetLastError()!=ERROR_IO_INCOMPLETE) {
						//		break;
						//	}
						//}
						success= HttpSendResponseEntityBody(
								server->request_queue,
								request->RequestId,
								0,
								1,
								datachunks,
								&byte_number,
								NULL,
								0,
								NULL,
								NULL
							);			
					}

					if (!server->runable) {
						break;
					}
					if (success==NO_ERROR) {
						pending_queue[pending_index].checksum = (response_check<<16)|received_check;
						printf("Query: CHECKSUM=%d INDEX=%d\n", 
							pending_queue[pending_index].checksum,
							pending_index
							);
						pending_index = (pending_index+1)%max_pending;

						//Debug:
						//printf("Coefs:\n");
						//for (i=0; i<256; ++i) {
						//	printf("%d ", real_coefs[i]);
						//}
						//printf("\n");
					}
				}
			} else
				if (!strncmp(request->pRawUrl, PCSTR_FETCH, request->RawUrlLength)) {
				received_index = getHttpHeaderIntValue(request, PCSTR_INDEX);
				received_check = getHttpHeaderIntValue(request, PCSTR_CHECKSUM);
				printf("Fetch: CHECKSUM=%d INDEX=%d\n",
					received_check, 
					received_index
					);

				if (received_check>=0
					&& received_index>=0 && received_index<max_pending
					&& pending_queue[received_index].checksum == received_check
				) {
					notfound = FALSE;

					pending_queue[received_index].checksum = -1;

					response.StatusCode = STATUS_CODE_OK;
					response.ReasonLength = OK_STR_LEN;
					response.pReason = PCSTR_OK;
					response.Headers.UnknownHeaderCount = 0;
					
					response.Headers.KnownHeaders[HttpHeaderContentLength].RawValueLength = blockLength_len;
					response.Headers.KnownHeaders[HttpHeaderContentLength].pRawValue = str_BlockLength;

					response.EntityChunkCount = 0;

					//for(;;) {
					//	memset(&oOverlapped, 0, sizeof(OVERLAPPED));
					//	HttpSendHttpResponse(
					//		server->request_queue,
					//		request.RequestId,
					//		0,
					//		&response,
					//		NULL,
					//		NULL,
					//		NULL,
					//		0,
					//		&oOverlapped,
					//		NULL
					//	);
					//	success = GetOverlappedResult(server->request_queue, &oOverlapped, &byte_number, FALSE);
					//	if (!server->runable || success || GetLastError()!=ERROR_IO_INCOMPLETE) {
					//		break;
					//	}
					//}

					success = HttpSendHttpResponse(
							server->request_queue,
							request->RequestId,
							HTTP_SEND_RESPONSE_FLAG_MORE_DATA,
							&response,
							NULL,
							&byte_number,
							NULL,
							0,
							NULL,
							NULL
						);

					if (!server->runable) {
						break;
					}

					if (success==NO_ERROR) {
						//Debug:
						//printf("Coefs:\n");
						//for (i=0; i<256; ++i) {
						//	printf("%d ", pending_queue[received_index].coefs[i]);
						//}
						//printf("\nRhs:\n");
						//for (i=0; i<16; ++i) {
						//	printf("%d ", block[i]);
						//}
						//gg = 0;
						//printf("\n");
						//for (i=0; i<256; ++i) {
						//	gg = gf16_add(gg, gf16_mul(1, pending_queue[received_index].coefs[i]));
						//	printf("%d ", gg);
						//}
						//

						encode_block(server->equs, pending_queue[received_index].coefs, block);


						//Debug:
						//if (gg!=block[0]) {
						//	printf("Error!\n");
						//	i = 1;
						//} else {
						//	i = 0;
						//}
						//printf("\n");
						//

						response.EntityChunkCount = 1;

						datachunks[0].FromMemory.BufferLength = block_length;
						datachunks[0].FromMemory.pBuffer = block;
						//for(;;) {
						//	memset(&oOverlapped, 0, sizeof(OVERLAPPED));
						//	HttpSendResponseEntityBody(
						//		server->request_queue,
						//		request.RequestId,
						//		0,
						//		1,
						//		datachunks,
						//		NULL,
						//		NULL,
						//		0,
						//		&oOverlapped,
						//		NULL
						//	);
						//	success = GetOverlappedResult(server->request_queue, &oOverlapped, &byte_number, FALSE);
						//	if (!server->runable || success || GetLastError()!=ERROR_IO_INCOMPLETE) {
						//		break;
						//	}
						//}
						success = HttpSendResponseEntityBody(
								server->request_queue,
								request->RequestId,
								0,
								1,
								datachunks,
								&byte_number,
								NULL,
								0,
								NULL,
								NULL
							);
					}

					if (!server->runable) {
						break;
					}

					//Debug:
					//if (i) {
					//	printf("Error!\n");
					//}
				}
			}
		}
		if (notfound) {
			response.StatusCode = STATUS_CODE_NOTFOUND;
			response.ReasonLength = NOTFOUND_STR_LEN;
			response.Headers.KnownHeaders[HttpHeaderContentLength].RawValueLength = 1;
			response.Headers.KnownHeaders[HttpHeaderContentLength].pRawValue = "0";
		}
		//for(;;) {
		//	memset(&oOverlapped, 0, sizeof(OVERLAPPED));
		//	HttpSendHttpResponse(
		//		server->request_queue,
		//		request.RequestId,
		//		0,
		//		&response,
		//		NULL,
		//		NULL,
		//		NULL,
		//		0,
		//		&oOverlapped,
		//		NULL
		//	);
		//	success = GetOverlappedResult(server->request_queue, &oOverlapped, &byte_number, FALSE);
		//	if (!server->runable || success || GetLastError()!=ERROR_IO_INCOMPLETE) {
		//		break;
		//	}
		//}
		success = HttpSendHttpResponse(
				server->request_queue,
				request->RequestId,
				0,
				&response,
				NULL,
				NULL,
				NULL,
				0,
				NULL,
				NULL
			);

	}

	for (i=0; i<max_pending; ++i) {
		free(pending_queue[i].coefs);
	}
	free(pending_queue);
	free(block);
	free(real_coefs);
}

void download_server_stop(download_server_t * server)
{
	server->runable = 0;
}